<?php
// +-------------------------------------------------------------------
// | 
// +-------------------------------------------------------------------
// | Copyright (c) 2009-2016 All rights reserved.
// +-------------------------------------------------------------------
namespace Kcdns\Service\File;

/**
 * 文件上传
 *
 * 前台使用
 * <link rel='stylesheet' type='text/css' href='/Public/static/kcs/kcs.css'>
 * <script src='/Public/static/kcs/kcs.src.js'></script>
 * <script src='/Public/static/kcs/kcs.js'></script>
 * <script> KCS.uploader(el,{ type:'image', width:'120', height:'120', url:'{:U('upload')}'}); </script>
 */
class Upload
{
    // 配置项
    protected $config = [
        // 文件上传临时目录
        'dir' => 'upload',
        // 文件临时目录的 web 路径, 未配置时使用 /+$dir 作为默认值
        'path' => '',
        // 允许上传的文件类型,不区分大小写
        'type' => 'jpg,jpeg,png,gif,doc,docx,pdf',
        // 文件上传域
        'name' => 'kcs_uploader_file',
        // 大小限制 : 10M
        'max' => 10485760,
        // 使用日期作为子目录
        'date' => true
    ];

    // 文件信息
    public $info = [
        // 文件名
        'name' => '',
        // 类型
        'type' => '',
        // web 路径
        'url' => '',
        // 文件路径
        'path' => '',
        // md5
        'md5' => ''
    ];

    // 错误通知类型
    protected $notifyInfo = array(
        1000 => '无法获取文件名',
        1001 => '文件名不合法',
        1002 => '创建临时文件失败',
        1003 => '文件上传错误',
        1004 => '文件未上传',
        1005 => '无法读取上传临时文件',
        1006 => '无法读取输入流',
        1007 => '无法移动文件到上传目录',
        1008 => '无法移动文件到临时目录',
        1009 => '创建上传目录失败',
        1010 => '未设置临时目录',
        1011 => '创建临时目录失败',
        1012 => '文件类型错误'
    );

    static $instace;

    public static function getInstance()
    {
        return self::$instace ?: self::$instace = new self();
    }

    /**
     * 处理上传请求
     */
    public function handle($config = array(), $return = true)
    {
        try {
            // 初始化配置
            $this->_loadConfig($config);

            // 获取文件信息
            $this->_loadFileinfo();

            // 文件保存路径
            $filePath = $this->config['dir'] . $this->info['savename'] . '.part';

            // 分段上传 : 当前分段序号
            $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;

            // 分段上传 : 分段总数
            $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;

            // 打开或创建临时文件
            $out = fopen("{$filePath}", $chunks ? "ab" : "wb") or $this->_notify(1002);

            // 从上传文件或输入中读取数据
            if (!empty($_FILES) && isset($_FILES[$this->config['name']])) {
                $_FILES[$this->config['name']]["error"] and $this->_notify(1003, $_FILES["file"]["error"]);
                is_uploaded_file($_FILES[$this->config['name']]["tmp_name"]) or $this->_notify(1004);
                $in = fopen($_FILES[$this->config['name']]["tmp_name"], "rb") or $this->_notify(1005);
            } else {
                $in = fopen("php://input", "rb") or $this->_notify(1006);
            }

            // TODO : 文件大小限制

            // 追加到临时文件
            while ($buff = fread($in, 4096)) {
                fwrite($out, $buff);
            }
            fclose($out);
            fclose($in);

            // 分段上传
            if ($chunks && $chunk < $chunks - 1) {
                $this->_notify(true);
            }

            // 上传完毕
            if (!$chunks || $chunk == $chunks - 1) {
                $md5 = md5_file($filePath);

                // 重命名上传文件
                $newFilename = $md5 . '.' . $this->info['type'];

                // 文件路径
                $this->info['path'] = $this->config['dir'] . $newFilename;

                // web 路径
                $this->info['url'] = str_replace('\\', '/', ltrim($this->config['path'], '.') . $newFilename);

                // 重命名临时文件
                file_exists($this->info['path']) or copy($filePath, $this->info['path']) or $this->_notify(1008, $filePath . '=>' . $this->info['path']);
                unlink($filePath);
                //生成缩略图
               // $image = new \Think\Image();
               // $image->open($this->info['path']);
                // 按照原图的比例生成一个最大为100*100的缩略图并保存
               // $image->thumb(100, 100)->save($this->info['path']); 
                $response = array(
                    'url' => $this->info['url'],
                    'name' => $this->info['name']
                );

                // 发送成功通知
                $this->_notify(true, $response);
            }
        } catch (\Exception $e) {
            $msg = $e->getMessage();
            return $return ? $msg : exit($msg);
        }
    }

    /**
     * 加载配置项
     */
    protected function _loadConfig($config)
    {
        $this->info = [];
        $this->config = array_merge($this->config, is_array($config) ? $config : []);

        // 上传文件类型
        $this->config['type'] = explode(',', $this->config['type']);

        // 检查上传目录
        $baseDir = $this->config['dir'];
        $this->config['dir'] or $this->_notify(1010);
        $this->config['dir'] = rtrim($this->config['dir'], '/') . '/';

        // 按照日期存储
        $this->config['date'] and $this->config['dir'] = $this->config['dir'] . date('Y-m-d/');

        file_exists($this->config['dir']) or mkdir($this->config['dir'], 0777, true) or $this->_notify(1011, $this->$this->config['dir']);

        // 修正上传目录 web 路径
        $this->config['path'] or $this->config['path'] = '/' . trim(ltrim($baseDir, '.'), '/');
        $this->config['path'] = rtrim($this->config['path'], '/') . '/';

        // 按照日期存储
        $this->config['date'] and $this->config['path'] = $this->config['path'] . date('Y-m-d/');
    }

    /**
     * 获得上传文件的文件信息
     */
    protected function _loadFileinfo()
    {
        if (isset($_REQUEST["name"])) {
            $filename = $_REQUEST["name"];
        } elseif (!empty($_FILES)) {
            $filename = @$_FILES[$this->config['name']]["name"];
        } else {
            // 获取文件名失败
            $this->_notify(1000);
        }
       
        $this->info['name'] = $filename;

        // 文件类型检测
        $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        in_array($filetype, $this->config['type']) or $this->_notify(1012, '文件类型无效 : ' . $filetype);

        $this->info['type'] = $filetype;
        $this->info['savename'] = md5(isset($_GET['_UPLOADER_ID_']) ? $_GET['_UPLOADER_ID_'] . $filename : $filename) . '.' . $filetype;
    }

    /**
     * 发送通知
     */
    protected function _notify($status = '', $logMsg = [])
    {
        $json = array(
            'jsonrpc' => '2.0',
            'id' => 'id'
        );

        if ($status !== true) {
            $this->info = false;
            $msg = $this->notifyInfo[$status] ?: $msg;
            $json['error'] = array(
                'code' => $status,
                'message' => $msg
            );
        } else {
            $logMsg or $this->info = false;
            $json['result'] = $logMsg;
        }

        throw new \Exception(json_encode($json));
    }
}